package com.example.exceldemo.controller;

import com.example.exceldemo.config.AppConfig;
import com.example.exceldemo.constants.DocConstants;
import com.example.exceldemo.exception.BizException;
import com.example.exceldemo.model.ApiResult;
import com.example.exceldemo.model.ReqInfoDTO;
import com.example.exceldemo.service.DocGeneratorService;
import com.example.exceldemo.service.ParsedExcelDataService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.awt.Desktop;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.net.URLEncoder;
import java.io.OutputStream;

/**
 * 文档生成控制器
 *
 * @author system
 */
@Slf4j
@Controller
@RequestMapping("/docs")
public class DocGeneratorController {

    /**
     * 文档生成服务
     */
    private final DocGeneratorService docGeneratorService;
    
    /**
     * 上传目录
     */
    private final String uploadDir;

    /**
     * 解析Excel数据服务
     */
    private final ParsedExcelDataService parsedExcelDataService;

    /**
     * 构造方法注入
     *
     * @param docGeneratorService 文档生成服务
     * @param appConfig 应用配置
     * @param parsedExcelDataService 解析Excel数据服务
     */
    public DocGeneratorController(DocGeneratorService docGeneratorService, AppConfig appConfig, ParsedExcelDataService parsedExcelDataService) {
        this.docGeneratorService = docGeneratorService;
        this.uploadDir = appConfig.getUpload().getDir();
        this.parsedExcelDataService = parsedExcelDataService;
        
        log.info("初始化DocGeneratorController，上传目录: {}", uploadDir);
    }

    /**
     * 文档生成页面
     *
     * @param model 模型
     * @return 页面名称
     */
    @GetMapping
    public String docGenPage(Model model) {
        return "docgen";
    }

    /**
     * 上传Excel文件
     *
     * @param file Excel文件
     * @return 响应结果
     */
    @PostMapping(value = "/upload", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ApiResult<Map<String, Object>> uploadExcel(@RequestParam("excelFile") MultipartFile file) {
        if (file == null || file.isEmpty()) {
            return ApiResult.fail("请选择Excel文件");
        }

        try {
            // 使用ParsedExcelDataService解析Excel并保存到数据库
            ParsedExcelDataService.ParsedExcelResult result = parsedExcelDataService.saveExcelData(file);
            
            // 返回已分析的需求信息以供前端显示
            Map<String, Object> resultData = new HashMap<>(4);
            resultData.put("reqInfoList", result.getReqInfoList());
            resultData.put("fileHash", result.getFileHash());

            return ApiResult.success("文件上传成功", resultData);
        } catch (BizException e) {
            log.error("业务异常: {}", e.getMessage());
            return ApiResult.fail(e.getCode(), e.getMessage());
        } catch (Exception e) {
            log.error("文件上传出错", e);
            return ApiResult.fail(BizException.fileUploadError("文件上传失败: " + e.getMessage()).getCode(), e.getMessage());
        }
    }

    /**
     * 生成文档
     *
     * @param fileHash 文件哈希值
     * @param selectedReqs 选定的需求列表
     * @return 响应结果
     */
    @PostMapping(value = "/generate", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ApiResult<Map<String, Object>> generateDocs(
            @RequestParam("fileHash") String fileHash,
            @RequestParam(value = "selectedReqs", required = false) List<String> selectedReqs,
            @RequestParam(value = "async", defaultValue = "true") boolean async) {
        
        if (!parsedExcelDataService.isExcelDataCached(fileHash)) {
            return ApiResult.fail(BizException.paramError("未找到对应的Excel解析数据，请重新上传文件").getCode(), "未找到对应的Excel解析数据");
        }
        
        try {
            // 从数据库中获取Excel解析数据
            List<ReqInfoDTO> reqInfoList = parsedExcelDataService.getExcelDataByHash(fileHash);
            
            if (async) {
                // 异步处理
                String metadata = selectedReqs != null ? String.join(",", selectedReqs) : "all";
                String taskId = docGeneratorService.generateDocumentsAsync(reqInfoList, selectedReqs, metadata);
                
                Map<String, Object> resultData = new HashMap<>(2);
                resultData.put("taskId", taskId);
                resultData.put("message", "文档生成任务已提交，请使用任务ID查询进度");
                
                return ApiResult.success("文档生成任务已提交", resultData);
            } else {
                // 同步处理
                String outputDir = docGeneratorService.generateDocumentsFromList(reqInfoList, selectedReqs);
                
                Map<String, Object> resultData = new HashMap<>(2);
                resultData.put("outputDir", outputDir);
                
                return ApiResult.success("文档生成成功", resultData);
            }
        } catch (BizException e) {
            log.error("业务异常: {}", e.getMessage());
            return ApiResult.fail(e.getCode(), e.getMessage());
        } catch (Exception e) {
            log.error("文档生成出错", e);
            return ApiResult.fail(BizException.docGenerateError("文档生成失败: " + e.getMessage()).getCode(), e.getMessage());
        }
    }

    /**
     * 设置页面
     *
     * @param model 模型
     * @return 页面名称
     */
    @GetMapping("/settings")
    public String settingsPage(Model model) {
        model.addAttribute("settings", docGeneratorService.getSettings());
        return "settings";
    }

    /**
     * 保存设置
     *
     * @param settings 设置参数
     * @return 响应结果
     */
    @PostMapping(value = "/settings", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ApiResult<Void> saveSettings(@RequestBody Map<String, String> settings) {
        try {
            docGeneratorService.saveSettings(settings);
            return ApiResult.success("设置保存成功", null);
        } catch (BizException e) {
            log.error("业务异常: {}", e.getMessage());
            return ApiResult.fail(e.getCode(), e.getMessage());
        } catch (Exception e) {
            log.error("保存设置出错", e);
            return ApiResult.fail(BizException.configError("保存设置失败: " + e.getMessage()).getCode(), e.getMessage());
        }
    }
    
    /**
     * 创建上传目录
     */
    private void createUploadDirectory() {
        File uploadDirFile = new File(uploadDir);
        if (!uploadDirFile.exists() && !uploadDirFile.mkdirs()) {
            throw BizException.fileUploadError("创建上传目录失败: " + uploadDir);
        }
    }
    
    /**
     * 保存上传的文件
     *
     * @param file 上传的文件
     * @return 文件保存路径
     * @throws Exception 保存文件异常
     */
    private String saveUploadedFile(MultipartFile file) throws Exception {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DocConstants.DatePattern.DATETIME));
        String originalFilename = file.getOriginalFilename();
        String filename = timestamp + "_" + originalFilename;
        Path filepath = Paths.get(uploadDir, filename);
        Files.write(filepath, file.getBytes());
        log.info("文件已保存到: {}", filepath);
        return filepath.toString();
    }

    /**
     * 测试目录是否存在且可写
     *
     * @param dirPath 目录路径
     * @return 响应结果
     */
    @PostMapping(value = "/test-directory", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ApiResult<Map<String, Boolean>> testDirectory(@RequestParam("dirPath") String dirPath) {
        if (!StringUtils.hasText(dirPath)) {
            return ApiResult.fail(BizException.paramError("目录路径不能为空").getCode(), "目录路径不能为空");
        }
        
        try {
            File directory = new File(dirPath);
            Map<String, Boolean> result = new HashMap<>(3);
            
            // 检查目录是否存在
            boolean exists = directory.exists();
            result.put("exists", exists);
            
            // 如果不存在，尝试创建
            boolean created = false;
            if (!exists) {
                created = directory.mkdirs();
                result.put("created", created);
            }
            
            // 检查是否可写
            boolean canWrite = directory.canWrite();
            result.put("canWrite", canWrite);
            
            return ApiResult.success(result);
        } catch (Exception e) {
            log.error("测试目录时出错: {}", dirPath, e);
            return ApiResult.fail(BizException.systemError("测试目录时出错: " + e.getMessage()).getCode(), "测试目录时出错: " + e.getMessage());
        }
    }

    /**
     * 尝试打开本地文件夹
     *
     * @param path 文件夹路径
     * @return 操作结果
     */
    @GetMapping(value = "/open-folder", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ApiResult<Boolean> openFolder(@RequestParam("path") String path) {
        if (!StringUtils.hasText(path)) {
            return ApiResult.fail(BizException.paramError("文件夹路径不能为空").getCode(), "文件夹路径不能为空");
        }
        
        // 服务器环境下不支持打开文件夹，返回成功但不执行实际操作
        log.info("服务器环境下不支持打开文件夹操作，路径: {}", path);
        return ApiResult.success(true);
    }
    
    /**
     * 处理文件打开请求（对于服务器环境，返回文件下载链接）
     *
     * @param path 文件路径
     * @return 操作结果
     */
    @GetMapping(value = "/open-file", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ApiResult<Map<String, String>> openFile(@RequestParam("path") String path) {
        if (!StringUtils.hasText(path)) {
            return ApiResult.fail(BizException.paramError("文件路径不能为空").getCode(), "文件路径不能为空");
        }
        
        File file = new File(path);
        if (!file.exists() || !file.isFile()) {
            return ApiResult.fail(BizException.paramError("指定的文件不存在").getCode(), "指定的文件不存在");
        }
        
        // 服务器环境下不支持直接打开文件，返回文件下载链接
        try {
            // 生成文件下载链接
            String fileName = file.getName();
            String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            
            // 创建下载链接 (这里可以根据实际需求调整)
            String downloadUrl = "/docs/download-file?path=" + URLEncoder.encode(path, "UTF-8");
            
            Map<String, String> result = new HashMap<>(2);
            result.put("downloadUrl", downloadUrl);
            result.put("fileName", fileName);
            
            log.info("为文件创建下载链接: {}", fileName);
            return ApiResult.success(result);
        } catch (Exception e) {
            log.error("处理文件打开请求时出错: {}", path, e);
            return ApiResult.fail(BizException.systemError("处理文件请求时出错: " + e.getMessage()).getCode(), 
                     "处理文件请求时出错: " + e.getMessage());
        }
    }
    
    /**
     * 单个文件下载
     * 
     * @param path 文件路径
     * @param response HTTP响应
     */
    @GetMapping("/download-file")
    public void downloadFile(@RequestParam("path") String path, HttpServletResponse response) {
        if (!StringUtils.hasText(path)) {
            throw BizException.paramError("文件路径不能为空");
        }
        
        File file = new File(path);
        if (!file.exists() || !file.isFile()) {
            throw BizException.paramError("指定的文件不存在: " + path);
        }
        
        try {
            // 设置响应头
            String fileName = file.getName();
            String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            
            // 根据文件类型设置Content-Type
            String contentType = getContentTypeByFileName(fileName);
            response.setContentType(contentType);
            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
            
            // 使用缓冲方式写入响应
            try (FileInputStream fis = new FileInputStream(file);
                 OutputStream os = response.getOutputStream()) {
                
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
                os.flush();
                
                log.info("文件下载完成: {}", fileName);
            }
        } catch (IOException e) {
            log.error("文件下载过程中出错", e);
            throw BizException.systemError("文件下载过程中出错: " + e.getMessage());
        }
    }
    
    /**
     * 根据文件名获取Content-Type
     * 
     * @param fileName 文件名
     * @return Content-Type
     */
    private String getContentTypeByFileName(String fileName) {
        fileName = fileName.toLowerCase();
        if (fileName.endsWith(".pdf")) {
            return "application/pdf";
        } else if (fileName.endsWith(".docx") || fileName.endsWith(".doc")) {
            return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        } else if (fileName.endsWith(".xlsx") || fileName.endsWith(".xls")) {
            return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        } else if (fileName.endsWith(".png")) {
            return "image/png";
        } else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
            return "image/jpeg";
        } else {
            return "application/octet-stream";
        }
    }

    /**
     * 将生成的文档打包为zip并提供下载
     *
     * @param dirPath 文档目录路径
     * @param response HTTP响应对象
     */
    @GetMapping("/download-zip")
    public void downloadDocumentsAsZip(@RequestParam("path") String dirPath, HttpServletResponse response) {
        if (!StringUtils.hasText(dirPath)) {
            throw BizException.paramError("目录路径不能为空");
        }
        
        File directory = new File(dirPath);
        if (!directory.exists() || !directory.isDirectory()) {
            throw BizException.paramError("指定的文件夹不存在: " + dirPath);
        }
        
        // 临时文件路径
        Path zipFilePath = null;
        
        try {
            // 使用原始文件夹名称作为压缩包名称
            String folderName = directory.getName();
            log.info("压缩目录: {}, 使用目录名称作为压缩包名称: {}", dirPath, folderName);
            
            // 需要处理中文名称
            String encodedFileName = URLEncoder.encode(folderName, "UTF-8").replaceAll("\\+", "%20");
            String zipFileName = folderName + ".zip";
            zipFilePath = Paths.get(System.getProperty("java.io.tmpdir"), zipFileName);
            
            // 压缩文件夹内容
            zipDirectory(directory, zipFilePath.toString());
            
            // 设置响应头
            response.setContentType("application/zip");
            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName + ".zip");
            
            // 使用缓冲方式写入响应，处理大文件
            try (FileInputStream fis = new FileInputStream(zipFilePath.toFile());
                 OutputStream os = response.getOutputStream()) {
                
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
                os.flush();
                
                log.info("压缩包下载完成: {}", zipFileName);
            } catch (IOException e) {
                // 特别处理ClientAbortException，这是客户端取消下载，不应视为服务器错误
                if (e.toString().contains("ClientAbortException")) {
                    log.warn("客户端中断了下载: {}", e.getMessage());
                } else {
                    throw e;
                }
            }
        } catch (IOException e) {
            log.error("创建或下载zip文件时出错", e);
            throw BizException.systemError("创建或下载zip文件时出错: " + e.getMessage());
        } finally {
            // 删除临时zip文件
            if (zipFilePath != null) {
                try {
                    Files.deleteIfExists(zipFilePath);
                } catch (IOException e) {
                    log.warn("删除临时压缩文件失败: {}", e.getMessage());
                }
            }
        }
    }
    
    /**
     * 将目录压缩为zip文件
     *
     * @param directoryToZip 要压缩的目录
     * @param zipFilePath 目标zip文件路径
     * @throws IOException 当压缩过程出错时
     */
    private void zipDirectory(File directoryToZip, String zipFilePath) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(zipFilePath);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            
            // 仅压缩最后一层文件，不使用目录名作为前缀
            zipDirectoryContents(directoryToZip, "", zos);
        }
    }
    
    /**
     * 递归压缩目录内容
     *
     * @param directory 当前处理的目录
     * @param baseName 相对基路径名
     * @param zos Zip输出流
     * @throws IOException 当压缩过程出错时
     */
    private void zipDirectoryContents(File directory, String baseName, ZipOutputStream zos) throws IOException {
        File[] files = directory.listFiles();
        if (files == null) {
            return;
        }
        
        byte[] buffer = new byte[1024];
        String entryPrefix = baseName.isEmpty() ? "" : baseName + "/";
        
        for (File file : files) {
            if (file.isDirectory()) {
                // 递归处理子目录
                zipDirectoryContents(file, entryPrefix + file.getName(), zos);
            } else {
                // 添加文件到zip
                try (FileInputStream fis = new FileInputStream(file)) {
                    // 创建zip入口 - 直接使用文件名，不包含目录路径
                    ZipEntry zipEntry = new ZipEntry(entryPrefix + file.getName());
                    zos.putNextEntry(zipEntry);
                    
                    // 写入文件内容
                    int length;
                    while ((length = fis.read(buffer)) > 0) {
                        zos.write(buffer, 0, length);
                    }
                    
                    zos.closeEntry();
                }
            }
        }
    }
} 